//
// Created by Administrator on 2016/12/9.
//

#include <SkyMaterial.h>
#include "Game.h"

using namespace std;
using namespace glm;
using namespace ZainTL;
using namespace ZainGL;
using namespace CEGUI;

default_random_engine Game::DefaultRandomEngine(time(0));

// RenderQuad() Renders a 1x1 quad in NDC, best used for framebuffer color targets
// and post-processing effects.
GLuint quadVAO = 0;
GLuint diffuseTex = 0;

void RenderQuad(Material *pMaterial, GLuint depthMap) {
    pMaterial->active();
    if (quadVAO == 0) {
        GLfloat quadVertices[] = {
                // Positions        // Texture Coords
                -1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
                -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
                1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
                1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
        };
        // Setup plane VAO
        glGenVertexArrays(1, &quadVAO);
        GLuint quadVBO;
        glGenBuffers(1, &quadVBO);
        glBindVertexArray(quadVAO);
        glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid *) 0);
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid *) (3 * sizeof(GLfloat)));
        //diffuseTex=loadTexture("/F/a.jpg")->textureID;
    }
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, depthMap);
    glUniform1i(glGetUniformLocation(pMaterial->mShaderLoc, "depthMap"), 0);
    glBindVertexArray(quadVAO);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glBindVertexArray(0);
}

void Game::reloadAsset() {
    PKGUtil *pkgUtil = new PKGUtil();
    pkgUtil->unpkg("../depths/datafiles/out.zpk", [this](FileDatas &fileData) {
        fileDatas.insert(make_pair(fileData.path, fileData));
    });
    delete (pkgUtil);

    directionalLight=new DirectionalLight();
    directionalLight->setPosition(vec3(0,0.5f,0.5f));
    lights.push_back(directionalLight);

    shadowMaterial = new Material("/shader/Shadow.v", "/shader/Shadow.f", &fileDatas);
    materials.push_back(shadowMaterial);

    shadowVisualMaterial = new Material("/shader/ShadowMapVisual.v", "/shader/ShadowMapVisual.f", &fileDatas);
    materials.push_back(shadowVisualMaterial);

    skyMaterial = new SkyMaterial("/shader/Sky.v", "/shader/Sky.f", &fileDatas);
    materials.push_back(skyMaterial);

    planeModel = new Model("/model/plane.FBX", false, MAXINSTANCENUM, &fileDatas);
    if (planeModel) {
        models.push_back(planeModel);
    }

    cubeModel = new Model("/model/cube.FBX", false, MAXINSTANCENUM, &fileDatas);
    if (cubeModel) {
        models.push_back(cubeModel);
    }

    meshMaterial = new Material("/shader/ZainGL.v", "/shader/ZainGL.f", &fileDatas);
    materials.push_back(meshMaterial);

    planeModel->mMaterial = meshMaterial;
    auto &planeMeshes = planeModel->mModels;
    for_each(planeMeshes.begin(), planeMeshes.end(), [this](Model *mesh) {
        mesh->rotate(radians(90.0f),vec3(1,0,0));
        mesh->translate(vec3(0,-60,0));
        mesh->mMaterial = meshMaterial;
    });

    cubeModel->mMaterial = meshMaterial;
    auto &cubeMeshes = cubeModel->mModels;
    for_each(cubeMeshes.begin(), cubeMeshes.end(), [this](Model *mesh) {
        mesh->mMaterial = meshMaterial;
    });

    sky = new Sky("/img/HornstullsStrand2/posx.jpg",
                  "/img/HornstullsStrand2/negx.jpg",
                  "/img/HornstullsStrand2/posy.jpg",
                  "/img/HornstullsStrand2/negy.jpg",
                  "/img/HornstullsStrand2/posz.jpg",
                  "/img/HornstullsStrand2/negz.jpg",
                  &fileDatas);
    sky->mMaterial = skyMaterial;
}

Game::Game(const char *title, int32_t width, int32_t height)
        : mRubik(nullptr), worldRight(1, 0, 0), mWidth(width), mHeight(height),
          mClickedFace(NONE) {
    /*sdl window*/
    SDL_Init(SDL_INIT_EVERYTHING);

    mSDLWindow = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height,
                                  SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);

/*#if defined(__linux__)
    // specifically request 3.2, because Mesa ignores core flag with version < 3
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
#endif*/

    mSDLContext = SDL_GL_CreateContext(mSDLWindow);
    /*SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);*/

    // disable native mouse cursor
    SDL_ShowCursor(0);

    /*Vertical Sync*/
    SDL_GL_SetSwapInterval(1);

    /*glew*/
    // Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions
    glewExperimental = GL_TRUE;
    // Initialize GLEW to setup the OpenGL Function pointers
    if (glewInit() != GLEW_OK) {
        cout << "Failed to initialize GLEW" << endl;
    }

    cout << "Rubik By Zain Liberty"  << endl;
    const GLubyte *version = glGetString(GL_VERSION);
    cout << "Supported GL VERSION " << version << endl;

    /*GL*/
    glEnable(GL_CULL_FACE);

    glClearColor(0, 1, 0, 1);

    glEnable(GL_DEPTH_TEST);/*depth testing*/

/*    glEnable(GL_BLEND);//alpha blending
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);*/

    initBulletPhysics();

    initCEGUI();
}

void Game::resetRubik() {
    if (mRubik) {
        delete (mRubik);
    }
    mRubik = new Rubik(dynamicsWorld, boxCollisionShape, CubeSize, cubeModel);

    /* For both eyes the combined visual field is 130-135° vertical and 200° horizontal*/
    float32_t aspectRatio = mWidth / mHeight;
    vec3 rubikCenter = mRubik->mCenter;
    camera.setFocusOnPosition(mRubik->mCenter);
    camera.setPosition(vec3(rubikCenter.x, rubikCenter.y, rubikCenter.z * 3 + rubikCenter.x * tan(radians(160.f) / 2)));
    projectionMat = perspective(radians(45.f), aspectRatio, 1.0f, camera.getPosition().z + rubikCenter.z * 100.f);
}

// convert SDL mouse button to CEGUI mouse button
MouseButton Game::SDLtoCEGUIMouseButton(const Uint8 &button) {
    switch (button) {
        case SDL_BUTTON_LEFT:
            return LeftButton;

        case SDL_BUTTON_MIDDLE:
            return MiddleButton;

        case SDL_BUTTON_RIGHT:
            return RightButton;

        case SDL_BUTTON_X1:
            return X1Button;

        case SDL_BUTTON_X2:
            return X2Button;

        default:
            return NoButton;
    }
}

Key::Scan Game::SDLtoCEGUIKey(SDL_Scancode key) {
    return static_cast<CEGUI::Key::Scan>(scanCodeToKeyNum[static_cast<int>(key)]);
}

void Game::SDLToCEGUITextUTF8(const char *utf8str) {
    static SDL_iconv_t cd = SDL_iconv_t(-1);

    if (cd == SDL_iconv_t(-1)) {
        // note: just "UTF-32" doesn't work as toFormat, because then you get BOMs, which we don't want.
        const char *toFormat = "UTF-32LE"; // TODO: what does CEGUI expect on big endian machines?
        cd = SDL_iconv_open(toFormat, "UTF-8");
        if (cd == SDL_iconv_t(-1)) {
            std::cerr << "Couldn't initialize SDL_iconv for UTF-8 to UTF-32!" << std::endl;
            return;
        }
    }

    // utf8str has at most SDL_TEXTINPUTEVENT_TEXT_SIZE (32) chars,
    // so we won't have have more utf32 chars than that
    Uint32 utf32buf[SDL_TEXTINPUTEVENT_TEXT_SIZE] = {0};

    // we'll convert utf8str to a utf32 string, saved in utf32buf.
    // the utf32 chars will be injected into cegui

    size_t len = strlen(utf8str);

    size_t inbytesleft = len;
    size_t outbytesleft = 4 * SDL_TEXTINPUTEVENT_TEXT_SIZE; // *4 because utf-32 needs 4x as much space as utf-8
    char *outbuf = (char *) utf32buf;
    size_t n = SDL_iconv(cd, &utf8str, &inbytesleft, &outbuf, &outbytesleft);

    if (n == size_t(-1)) // some error occured during iconv
    {
        std::cerr << "Converting UTF-8 string " << utf8str << " from SDL_TEXTINPUT to UTF-32 failed!" << std::endl;
    }

    for (int i = 0; i < SDL_TEXTINPUTEVENT_TEXT_SIZE; ++i) {
        if (utf32buf[i] == 0)
            break; // end of string

        CEGUI::System::getSingleton().getDefaultGUIContext().injectChar(utf32buf[i]);
    }

    // reset cd so it can be used again
    SDL_iconv(cd, NULL, &inbytesleft, NULL, &outbytesleft);
}

void Game::run() {
    windowSizing(mWidth, mHeight);
    /*loop*/
    SDL_Event sdl_event;
    while (!shouldClose) {
        vec3 rubikRight, rubikUp, rubikForward;
        if (mRubik) {
            rubikRight = mRubik->mTransByMCenter[0];
            rubikUp = mRubik->mTransByMCenter[1];
            rubikForward = mRubik->mTransByMCenter[2];
        }

        /*events*/
        while (SDL_PollEvent(&sdl_event)) {
            switch (sdl_event.type) {
                case SDL_QUIT:
                    shouldClose = true;
                    break;
                case SDL_WINDOWEVENT: {
                    SDL_WindowEvent *event = &sdl_event.window;
                    if (event->event == SDL_WINDOWEVENT_RESIZED) {
                        windowSizing(event->data1, event->data2);
                    } else if (sdl_event.window.event == SDL_WINDOWEVENT_LEAVE) {
                        /*inject into CEGUI*/
                        CEGUI::System::getSingleton().getDefaultGUIContext().injectMouseLeaves();
                    }
                }
                    break;
                case SDL_MOUSEBUTTONDOWN: {
                    SDL_MouseButtonEvent mouseButtonEvent = sdl_event.button;

                    /*inject into CEGUI*/
                    CEGUI::System::getSingleton().getDefaultGUIContext().injectMouseButtonDown(
                            SDLtoCEGUIMouseButton(mouseButtonEvent.button));

                    if (popupMenu->isVisible())//discard mouse action for game when menu is visible
                        break;

                    if (mouseButtonEvent.button == SDL_BUTTON_LEFT) {
                        glm::vec3 out_origin;
                        glm::vec3 out_direction;
                        screenPosToWorldRay(mouseButtonEvent.x, mouseButtonEvent.y, mWidth, mHeight,
                                            camera.getViewMatrix(),
                                            projectionMat, out_origin,
                                            out_direction);

                        float32_t viewDisToRubikCenter = distance(camera.getPosition(), mRubik->mCenter);
                        glm::vec3 end = out_origin + out_direction * (2.0f * viewDisToRubikCenter);

                        btCollisionWorld::ClosestRayResultCallback RayCallback(
                                btVector3(out_origin.x, out_origin.y, out_origin.z),
                                btVector3(end.x, end.y, end.z));
                        dynamicsWorld->rayTest(btVector3(out_origin.x, out_origin.y, out_origin.z),
                                               btVector3(end.x, end.y, end.z), RayCallback);
                        if (RayCallback.hasHit()) {
                            lastLeftButtonPoint = {mouseButtonEvent.x, mouseButtonEvent.y};

                            leftButtonClickPosWorld = out_origin;

                            clickedCube = (Cube *) RayCallback.m_collisionObject->getUserPointer();
                            vec3 clickedCubeCenter = Cube::instanceTransforms[clickedCube->mIndex][3];

                            btVector3 hitPointWorld = RayCallback.m_hitPointWorld;

                            vec3 hitPointWorldGLM(hitPointWorld.x(), hitPointWorld.y(), hitPointWorld.z());

                            /*detected clicked face*/
                            vec3 hitToCenter = glm::normalize(hitPointWorldGLM - clickedCubeCenter);
                            float32_t xDot = dot(hitToCenter, rubikRight);
                            float32_t yDot = dot(hitToCenter, rubikUp);
                            float32_t zDot = dot(hitToCenter, rubikForward);
                            float32_t maxDot = fmax(fabs(xDot), fabs(yDot));
                            maxDot = fmax(fabs(maxDot), fabs(zDot));
                            if (maxDot == fabs(xDot)) {
                                mRubik->mClickedFace = xDot > 0 ? B : B1;
                            } else if (maxDot == fabs(yDot)) {
                                mRubik->mClickedFace = yDot > 0 ? C : C1;
                            } else if (maxDot == fabs(zDot)) {
                                mRubik->mClickedFace = zDot > 0 ? A : A1;
                            }
                        }
                    }
                }
                    break;
                case SDL_MOUSEBUTTONUP: {
                    SDL_MouseButtonEvent mouseButtonEvent = sdl_event.button;

                    /*inject into CEGUI*/
                    CEGUI::System::getSingleton().getDefaultGUIContext().injectMouseButtonUp(
                            SDLtoCEGUIMouseButton(mouseButtonEvent.button));

                    if (popupMenu->isVisible())//discard mouse action for game when menu is visible
                        break;

                    if (mouseButtonEvent.button == SDL_BUTTON_RIGHT) {
                        //right button released
                        rightButtonPressedPoint = {-1, -1};
                        virtualRightButtonPoint = lastVirtualRightButtonPoint = {0};
                    } else if (mouseButtonEvent.button == SDL_BUTTON_LEFT) {
                        //left button released
                        if (!mRubik->autoRotateSliceRadian) {
                            float32_t fabsSliceRotatedRadian = fabs(mRubik->mManualRotatedRadian);
                            while (fabsSliceRotatedRadian - PI >= 0) {
                                fabsSliceRotatedRadian -= PI;
                            }
                            if (fabsSliceRotatedRadian > -PId4 && fabsSliceRotatedRadian < PId4) {
                                mRubik->autoRotateSliceRadian =
                                        mRubik->mManualRotatedRadian > 0 ? (0 - fabsSliceRotatedRadian) : -(0 -
                                                                                                            fabsSliceRotatedRadian);
                            } else if (fabsSliceRotatedRadian > PId4 && fabsSliceRotatedRadian < PId4 * 3) {
                                mRubik->autoRotateSliceRadian =
                                        mRubik->mManualRotatedRadian > 0 ? (PId2 - fabsSliceRotatedRadian) : -(PId2 -
                                                                                                               fabsSliceRotatedRadian);
                            } else if (fabsSliceRotatedRadian > PId4 * 3 && fabsSliceRotatedRadian < PId4 * 5) {
                                mRubik->autoRotateSliceRadian =
                                        mRubik->mManualRotatedRadian > 0 ? (PI - fabsSliceRotatedRadian) : -(PI -
                                                                                                             fabsSliceRotatedRadian);
                            }
                        }
                    }
                }
                    break;
                case SDL_MOUSEMOTION: {
                    SDL_MouseMotionEvent mouseMotion = sdl_event.motion;

                    /*inject into CEGUI*/
                    CEGUI::System::getSingleton().getDefaultGUIContext().injectMousePosition(
                            static_cast<float>(mouseMotion.x),
                            static_cast<float>(mouseMotion.y));

                    if (popupMenu->isVisible() || !mRubik)//discard mouse action for game when menu is visible or rubik not alive
                        break;

                    if (mouseMotion.state == SDL_BUTTON(SDL_BUTTON_RIGHT)) {
                        if (mRubik->autoRotateSliceRadian) {
                            break;
                        }
                        if (rightButtonPressedPoint.x == -1 || rightButtonPressedPoint.y == -1) {
                            rightButtonPressedPoint = {mouseMotion.x, mouseMotion.y};
                        } else {
                            //move with right button pressing
                            BTNPOINT bias;
                            bias.x = mouseMotion.x - rightButtonPressedPoint.x;
                            bias.y = mouseMotion.y - rightButtonPressedPoint.y;

                            virtualRightButtonPoint.x += bias.x;
                            virtualRightButtonPoint.y += bias.y;

                            SDL_WarpMouseInWindow(mSDLWindow, rightButtonPressedPoint.x, rightButtonPressedPoint.y);
                        }
                    } else if (mouseMotion.state == SDL_BUTTON(SDL_BUTTON_LEFT)) {
                        if (mRubik->autoRotateSliceRadian) {
                            break;
                        }
                        if (mRubik->mClickedFace != NONE) {
                            if (mRubik->mRotateVector == vec3(-1)) {
                                float32_t hChange = mouseMotion.x - lastLeftButtonPoint.x;
                                float32_t vChange = mouseMotion.y - lastLeftButtonPoint.y;
                                float32_t changeDis = sqrt(vChange * vChange + hChange * hChange);
                                if (changeDis < 4)break;

                                glm::vec3 out_origin;
                                glm::vec3 out_direction;
                                screenPosToWorldRay(mouseMotion.x, mouseMotion.y, mWidth, mHeight,
                                                    camera.getViewMatrix(),
                                                    projectionMat, out_origin,
                                                    out_direction);

                                vec3 dragFaceVector = normalize(out_origin - leftButtonClickPosWorld);
                                //cout<<dragFaceVector.x<<","<<dragFaceVector.y<<","<<dragFaceVector.z<<endl;

                                /*get the most parallel Rubik Axes to assign to dragFaceVector*/
                                float32_t dotX = dot(dragFaceVector, rubikRight);
                                float32_t dotY = dot(dragFaceVector, rubikUp);
                                float32_t dotZ = dot(dragFaceVector, rubikForward);
                                float32_t maxDot = fmax(fabs(dotX), fabs(dotY));
                                maxDot = fmax(fabs(maxDot), fabs(dotZ));
                                if (maxDot == fabs(dotX)) {
                                    dragFaceVector = rubikRight;
                                } else if (maxDot == fabs(dotY)) {
                                    dragFaceVector = rubikUp;
                                } else if (maxDot == fabs(dotZ)) {
                                    dragFaceVector = rubikForward;
                                }

                                //find same slice cubes by dragFaceVector
                                AXES rotateAxesPivot;
                                switch (mRubik->mClickedFace) {
                                    default:
                                        break;
                                    case A:
                                    case A1:
                                        if (dragFaceVector == rubikRight || dragFaceVector == rubikForward) {
                                            rotateAxesPivot = Y;
                                        } else if (dragFaceVector == rubikUp) {
                                            rotateAxesPivot = X;
                                        }
                                        break;
                                    case B:
                                    case B1:
                                        if (dragFaceVector == rubikForward || dragFaceVector == rubikRight) {
                                            rotateAxesPivot = Y;
                                        } else if (dragFaceVector == rubikUp) {
                                            rotateAxesPivot = Z;
                                        }
                                        break;
                                    case C:
                                    case C1:
                                        if (dragFaceVector == rubikForward || dragFaceVector == rubikUp) {
                                            rotateAxesPivot = X;
                                        } else if (dragFaceVector == rubikRight) {
                                            rotateAxesPivot = Z;
                                        }
                                        break;
                                }
                                if (!mRubik->mSameSliceCubes.size()) {
                                    mRubik->findSameSliceCubes(clickedCube, rotateAxesPivot);
                                }
                                //findSameSliceCubes(clickedCube, rotateAxesPivot);
                            } else {
                                float32_t hChange = mouseMotion.x - lastLeftButtonPoint.x;
                                float32_t vChange = mouseMotion.y - lastLeftButtonPoint.y;
                                lastLeftButtonPoint = {mouseMotion.x, mouseMotion.y};
                                float32_t maxChangeABS = fmax(fabs(hChange), fabs(vChange));
                                float32_t changeDis = sqrt(vChange * vChange + hChange * hChange);
                                if ((maxChangeABS == fabs(vChange) && vChange < 0) ||
                                    (maxChangeABS == fabs(hChange) && hChange < 0)) {
                                    changeDis = -changeDis;
                                }

                                float32_t radian = changeDis * leftButtonSens * deltaTime;
                                mRubik->rotateSlice(radian);
                            }
                        }
                    }
                }
                    break;
                case SDL_KEYDOWN: {
                    /*inject into CEGUI*/
                    CEGUI::System::getSingleton().getDefaultGUIContext().injectKeyDown(
                            SDLtoCEGUIKey(sdl_event.key.keysym.scancode));

                    if (popupMenu->isVisible())//discard keyboard action for game when menu is visible
                        break;

                    if (holdingKeyNum > MAX_KEY_NUM) {
                        break;
                    }

                    SDL_KeyboardEvent keyboardEvent = sdl_event.key;
                    SDL_Keysym keysym = keyboardEvent.keysym;

                    int emptyIndex = -1;
                    for (uint8_t i = 0; i < MAX_KEY_NUM; i++) {
                        if (holdingKeys[i] == keysym.sym) {
                            break;
                        }
                        if (holdingKeys[i] == 0) {
                            emptyIndex = i;
                            break;
                        }
                    }
                    holdingKeys[emptyIndex] = keysym.sym;
                    holdingKeyNum++;
                }
                    break;
                case SDL_KEYUP: {
                    /*inject into CEGUI*/
                    CEGUI::System::getSingleton().getDefaultGUIContext().injectKeyUp(
                            SDLtoCEGUIKey(sdl_event.key.keysym.scancode));

                    if (popupMenu->isVisible())//discard keyboard action for game when menu is visible
                        break;

                    SDL_KeyboardEvent keyboardEvent = sdl_event.key;
                    SDL_Keysym keysym = keyboardEvent.keysym;

                    uint8_t findIndex;
                    for (findIndex = 0; findIndex < MAX_KEY_NUM; findIndex++) {
                        if (holdingKeys[findIndex] == keysym.sym)
                            break;
                    }
                    if (findIndex == MAX_KEY_NUM) {
                        break;
                    }
                    holdingKeys[findIndex] = 0;
                    holdingKeyNum--;
                }
                    break;

                case SDL_TEXTINPUT:
                    SDLToCEGUITextUTF8(sdl_event.text.text);
                    break;

                default:
                    break;
            }
        }

        uint32_t now = SDL_GetTicks();
        deltaTime = now - lastRenderTime;
        lastRenderTime = now;
        if (now - lastCountFPSTime >= 500) {
            String fpsCountStr = "fps:";
            fpsCountStr += to_string(fpsCount * 2);
            fpsCountLabel->setText(fpsCountStr);
            lastCountFPSTime = now;
            fpsCount = 0;
        } else {
            ++fpsCount;
        }

        // inject time pulses
        System::getSingleton().injectTimePulse(deltaTime);
        System::getSingleton().getDefaultGUIContext().injectTimePulse(deltaTime);

        /*key iter*/
        for (uint8_t keyIndex = 0; keyIndex < MAX_KEY_NUM; ++keyIndex) {
            int32_t key = holdingKeys[keyIndex];
            if (key) {
                float32_t deltaSpeed = cameraKeyMoveSpeed * deltaTime;
                switch (key) {
                    case SDLK_w:
                        if(distance(camera.getPosition(),camera.getFocusOnPosition())>=150.0f) {
                            camera.setPosition(camera.getPosition() + deltaSpeed * -camera.getForward());
                        }
                        break;
                    case SDLK_a:
                        camera.setPosition(camera.getPosition() + deltaSpeed * -camera.getRight());
                        break;
                    case SDLK_s:
                        if(distance(camera.getPosition(),camera.getFocusOnPosition())<=700.0f){
                            camera.setPosition(camera.getPosition() + deltaSpeed * camera.getForward());
                        }
                        break;
                    case SDLK_d:
                        camera.setPosition(camera.getPosition() + deltaSpeed * camera.getRight());
                        break;
                    default:
                        break;
                }
            }
        }

        /*right mouse button rotate whole Rubik*/
        uint32_t mouseState = SDL_GetMouseState(nullptr, nullptr);
        if (mRubik && !mRubik->autoRotateSliceRadian && mouseState == SDL_BUTTON(SDL_BUTTON_RIGHT)) {
            BTNPOINT rightButtonBias;
            rightButtonBias.x = virtualRightButtonPoint.x - lastVirtualRightButtonPoint.x;
            rightButtonBias.y = virtualRightButtonPoint.y - lastVirtualRightButtonPoint.y;

            float horizontalChange = rightButtonBias.x * deltaTime * rightMouseSens;
            float verticalChange = rightButtonBias.y * deltaTime * rightMouseSens;

            //transform all cubes by Rubik's center
            mat4 transformByRubikCenter;
            transformByRubikCenter = translate(transformByRubikCenter, mRubik->mCenter);
            transformByRubikCenter = rotate(transformByRubikCenter, horizontalChange, camera.getUp());
            transformByRubikCenter = rotate(transformByRubikCenter, verticalChange, camera.getRight());
            transformByRubikCenter = translate(transformByRubikCenter, -mRubik->mCenter);

            auto &cubes = mRubik->mCubes;
            uint32_t cubesNum = mRubik->mCubes.size();
            for (int i = 0; i < cubesNum; ++i) {
                mat4 newTrans = transformByRubikCenter * Cube::instanceTransforms[cubes[i]->mIndex];
                cubes[i]->resetTransform(newTrans, nullptr);
            }

            mRubik->mTransByMCenter = transformByRubikCenter * mRubik->mTransByMCenter;

            lastVirtualRightButtonPoint = virtualRightButtonPoint;
        }

        if (mRubik) {
            mRubik->onTick(deltaTime);
            if (mRubik->bAutoMessing) {
                cameraBegin += cameraSpeed * deltaTime;
                camera.setPosition(cameraBegin);
            } else {
                if (!mRubik->bWin && now - lastCountTime >= 1000) {
                    int timeCount = stoi(timeCountLabel->getText().c_str()) + 1;
                    timeCountLabel->setText(to_string(timeCount));
                    lastCountTime = now;
                }
            }
            if (mRubik->bWin) {
                String winStr = (utf8 *) "成功复原魔方！用时：";
                winStr.append(timeCountLabel->getText()).append((utf8 *) "秒");
                WinWindow->getChild("winLabel")->setText(winStr);
                WinWindow->show();
            }
        }

        /*update bullet dynamicsWorld*/
        dynamicsWorld->stepSimulation(deltaTime, 1, deltaTime);

        mat4  viewMatrix=camera.getViewMatrix();
        vec3 viewPos=camera.getPosition();

        /*first render to shadowmap*/
        shadowMaterial->update(viewMatrix, viewPos, projectionMat, lights, mWidth, mHeight);
        directionalLight->beginRenderToShadowMap(mWidth, mHeight, shadowMaterial->mShaderLoc);
        //planeModel->renderToShadowMap();
        cubeModel->renderToShadowMap(&Cube::instanceTransforms);
        directionalLight->endRenderToShadowMap();

        /*Render Normal*/
        glViewport(0, 0, mWidth, mHeight);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        /*render models*/
        meshMaterial->update(viewMatrix, viewPos, projectionMat, lights, mWidth, mHeight);
        planeModel->draw();
        cubeModel->draw(&Cube::instanceTransforms);

        //render sky
        skyMaterial->update(viewMatrix, viewPos, projectionMat, lights, mWidth, mHeight);
        sky->draw();

        //debug shadow visual
        /*glViewport(0,0,mWidth,mHeight);
        RenderQuad(shadowVisualMaterial, Light::ShadowMapTextureLoc);*/

        //render CEGUI
        glDepthFunc(GL_ALWAYS);
        ceguiRenderer->beginRendering();
        System::getSingleton().renderAllGUIContexts();
        ceguiRenderer->endRendering();
        glDepthFunc(GL_LESS);

        SDL_GL_SwapWindow(mSDLWindow);
    }
    cout << "Shutting down rubik game..." << endl;

    // destroy system and renderer
    System::destroy();
    OpenGL3Renderer::destroy(*ceguiRenderer);
    ceguiRenderer = 0;

    // delete SDL GL context
    SDL_GL_DeleteContext(mSDLContext);

    // destroy SDL window
    SDL_DestroyWindow(mSDLWindow);

    // cleanup SDL
    SDL_Quit();
}

void Game::windowSizing(Sint32 width, Sint32 height) {
    mWidth = width;
    mHeight = height;

    System::getSingleton().notifyDisplaySizeChanged(Size<float>(mWidth, mHeight));
    glViewport(0, 0, mWidth, mHeight);
    //ceguiRenderer->setDisplaySize(Size<float>(mWidth, mHeight));//this function may internally call glViewPort()

    /* For both eyes the combined visual field is 130-135° vertical and 200° horizontal*/
    float32_t aspectRatio = mWidth / mHeight;
    if (mRubik) {
        vec3 rubikCenter = mRubik->mCenter;

        camera.setPosition(vec3(rubikCenter.x, rubikCenter.y, rubikCenter.z * 3 + rubikCenter.x * tan(radians(160.f) / 2)));
        projectionMat = perspective(radians(45.f), aspectRatio, 1.0f, camera.getPosition().z + rubikCenter.z * 100.f);
    } else {
        projectionMat = perspective(radians(45.f), aspectRatio, 1.0f, 10.f);
    }

    Light::recreateShadowMap(width, height);
}

void Game::initCEGUI() {
    ceguiRenderer = &OpenGL3Renderer::bootstrapSystem();
    DefaultResourceProvider *defaultResourceProvider = static_cast<DefaultResourceProvider *>(System::getSingleton().getResourceProvider());
    defaultResourceProvider->setResourceGroupDirectory("fonts", "../depths/datafiles/fonts");
    defaultResourceProvider->setResourceGroupDirectory("imagesets", "../depths/datafiles/imagesets");
    defaultResourceProvider->setResourceGroupDirectory("layouts", "../depths/datafiles/layouts");
    defaultResourceProvider->setResourceGroupDirectory("looknfeel", "../depths/datafiles/looknfeel");
    defaultResourceProvider->setResourceGroupDirectory("schemes", "../depths/datafiles/schemes");
    defaultResourceProvider->setResourceGroupDirectory("lua_scripts", "../depths/datafiles/lua_scripts");

    ImageManager::setImagesetDefaultResourceGroup("imagesets");
    Scheme::setDefaultResourceGroup("schemes");
    Font::setDefaultResourceGroup("fonts");
    WidgetLookManager::setDefaultResourceGroup("looknfeel");
    WindowManager::setDefaultResourceGroup("layouts");
    ScriptModule::setDefaultResourceGroup("lua_scripts");

    XMLParser *parser = System::getSingleton().getXMLParser();
    if (parser->isPropertyPresent("SchemaDefaultResourceGroup"))
        parser->setProperty("SchemaDefaultResourceGroup", "schemas");

    SchemeManager::getSingleton().createFromFile("Rubik.scheme");
    SchemeManager::getSingleton().createFromFile("TaharezLook.scheme");

    FontManager::getSingleton().createFromFile("Rubik-18.font");
    FontManager::getSingleton().createFromFile("Rubik-TimeCount.font");
    System::getSingleton().getDefaultGUIContext().setDefaultFont("Rubik-18");

    System::getSingleton().getDefaultGUIContext().getMouseCursor().setDefaultImage(
            "TaharezLook/MouseArrow");//mouse arrow imageset
    System::getSingleton().getDefaultGUIContext().setDefaultTooltipType("Rubik/Tooltip");

    System::getSingleton().getDefaultGUIContext().setRootWindow(
            WindowManager::getSingleton().loadLayoutFromFile("Rubik.layout"));

    Window *CEGUIRootWindow = System::getSingleton().getDefaultGUIContext().getRootWindow();
    CEGUIRootWindow->activate();

    popupMenu = CEGUIRootWindow->getChild("PopupMenu");
    settingMenu = CEGUIRootWindow->getChild("settingMenu");

    // subscribe button handler
    CEGUIRootWindow->getChild("PopupMenu/btnNewGame")->subscribeEvent(PushButton::EventClicked,
                                                                      Event::Subscriber(&Game::newGame, this));

    CEGUIRootWindow->getChild("PopupMenu/btnExit")->subscribeEvent(PushButton::EventClicked,
                                                                   [this]() { shouldClose = true; });


    CEGUIRootWindow->getChild("PopupMenu/btnSetting")->subscribeEvent(PushButton::EventClicked,
                                                                      [this]() { settingMenu->show(); });

    Window *dimenLabel = CEGUIRootWindow->getChild("settingMenu/dimenLabel");

    Slider *dimenSlider = (Slider *) CEGUIRootWindow->getChild("settingMenu/dimenSlider");

    updateDimenLabel(dimenLabel, dimenSlider);

    dimenSlider->subscribeEvent(Slider::EventValueChanged, [=]() {
        updateDimenLabel(dimenLabel, dimenSlider);
    });

    dimenSlider->subscribeEvent(Slider::EventThumbTrackEnded, [=]() {
        uint16_t dimen = round(dimenSlider->getCurrentValue());
        dimenSlider->setCurrentValue(dimen);
    });

    CEGUIRootWindow->getChild("settingMenu/btnDimenCancle")->subscribeEvent(PushButton::EventClicked, [=]() {
        settingMenu->hide();
    });
    CEGUIRootWindow->getChild("settingMenu/btnDimenOK")->subscribeEvent(PushButton::EventClicked, [=]() {
        popupMenu->hide();
        settingMenu->hide();
        Rubik::Dimen = dimenSlider->getCurrentValue() + 2;
        newGame(EventArgs());
    });

    CEGUIRootWindow->subscribeEvent(Window::EventKeyDown, Event::Subscriber(&Game::CEGUIKeyEventRootWindow, this));

    timeCountLabel = CEGUIRootWindow->getChild("timeCountLabel");
    fpsCountLabel = CEGUIRootWindow->getChild("fpsCountLabel");

    Window *HelpWindow = CEGUIRootWindow->getChild("HelpWindow");
    HelpWindow->subscribeEvent(FrameWindow::EventCloseClicked, [=]() {
        HelpWindow->hide();
        HelpWindow->invalidate();
    });
    CEGUIRootWindow->getChild("PopupMenu/btnHelp")->subscribeEvent(PushButton::EventClicked, [=]() {
        HelpWindow->show();
    });

    WinWindow = CEGUIRootWindow->getChild("WinWindow");
    WinWindow->subscribeEvent(FrameWindow::EventCloseClicked, [=]() {
        WinWindow->hide();
        WinWindow->invalidate();
        newGame(EventArgs());
    });
}

void Game::initBulletPhysics() {
    /*Initialize Bullet. This strictly follows http://bulletphysics.org/mediawiki-1.5.8/index.php/Hello_World*/
    // Build the broadphase
    btBroadphaseInterface *broadphase = new btDbvtBroadphase();

    // Set up the collision configuration and dispatcher
    btDefaultCollisionConfiguration *collisionConfiguration = new btDefaultCollisionConfiguration();
    btCollisionDispatcher *dispatcher = new btCollisionDispatcher(collisionConfiguration);

    // The actual physics solver
    btSequentialImpulseConstraintSolver *solver = new btSequentialImpulseConstraintSolver;

    // The world.
    dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
    dynamicsWorld->setGravity(btVector3(0, -9.81f, 0));

    // all boxes use the same collision shape : the half-extent of the model
    float halfExtent = CubeSize / 2;
    boxCollisionShape = new btBoxShape(btVector3(halfExtent, halfExtent, halfExtent));
}

void Game::CEGUIKeyEventRootWindow(const CEGUI::EventArgs &args) {
    const CEGUI::KeyEventArgs &keyEvent = static_cast<const CEGUI::KeyEventArgs &>(args);
    if (CEGUI::Key::Escape == keyEvent.scancode) {
        if (popupMenu->isVisible()) {
            popupMenu->hide();
            popupMenu->invalidate();
        } else {
            popupMenu->show();
        }
    }
}

void Game::newGame(const CEGUI::EventArgs &args) {
    timeCountLabel->setText(to_string(0));
    resetRubik();

    autoMessCount = round(log(Rubik::Dimen + 10));
    mRubik->mAutoMessCount = autoMessCount;

    vec3 rubikCenter = mRubik->mCenter;
    uniform_int_distribution<> u(-8, 8);
    cameraBegin = vec3(u(DefaultRandomEngine) * rubikCenter.x, u(DefaultRandomEngine) * rubikCenter.y, rubikCenter.z);
    cameraEnd = vec3(rubikCenter.x, rubikCenter.y, rubikCenter.z * 2 + rubikCenter.x * tan(radians(160.f) / 2));
    cameraTotalTime = autoMessCount * mRubik->PId2 / mRubik->autoRotateSpeed;
    cameraSpeed = (cameraEnd - cameraBegin) / cameraTotalTime;

    popupMenu->hide();
    popupMenu->invalidate();
}

void Game::updateDimenLabel(Window *dimenLabel, Slider *dimenSlider) {
    CEGUI::String labelStr = (utf8 *) "魔方层数：";
    uint16_t dimen = round(dimenSlider->getCurrentValue()) + 2;
    labelStr += std::to_string(dimen);
    dimenLabel->setText(labelStr);
}
